/*
**  SweepMine Game Core
** 
**  @author xuwaters@qq.com
**  @release 2008
*/
#include "stdafx.h"
#include "game.h"
#include <string.h>
#include <time.h>
#include <stdlib.h>

#define FALSE 0
#define TRUE  1

static const int DEF_ROW = 10;
static const int DEF_COL = 10;
static const int DEF_MINE = 20;
static const int D[2][8] = {
    { +1, +1,  0, -1, -1, -1,  0, +1 },
    {  0, +1, +1, +1,  0, -1, -1, -1 }
};

static int g_nRow;
static int g_nCol;
static int g_nMines;
static int g_nFlags;

static int g_nTotal;
static int g_nRemain;

static int g_nMaskRow;
static int g_nMaskCol;
static int g_nMaskDim; // -1 means no mask

static int g_bGameStarted;
static int g_bGameOver;
static int g_bMapGenerated;

static int g_nPoint[MAXN*MAXN];
static int g_nOpenList[MAXN*MAXN], g_nListSize;

/*
** ['0','8']: mine number
**    '+'   : mine
*/
static char g_realMap[MAXN][MAXN];

/*
** '+': Opened Mine
** '#': Clicked Mine
** 'x': Error Flag
** ' ': Space
** 'f': Flag
** '?': Not Sure
** '0'..'8': Opened Number of Mines
*/
static char g_showMap[MAXN][MAXN];

// Callback functions
GameCallbacks g_fCallbacks;

// implements
inline int out(int r, int c)
{
    return r < 0 || r >= g_nRow || c < 0 || c >= g_nCol;
}
inline void initRandom()
{
    srand((unsigned int)time(NULL));
}
inline int random(int size)
{
    if (size <= 0) return 0;
    return rand() % size;
}
inline void swap(int* a, int* b)
{
    int t = *a;
    *a = *b;
    *b = t;
}
void clearMap()
{
    int i;
    for (i = 0; i < g_nRow; i++)
    {
        memset(g_realMap[i], '0', g_nCol * sizeof(g_realMap[i][0]));
        memset(g_showMap[i], ' ', g_nCol * sizeof(g_showMap[i][0]));
    }
    for (i = 0; i < g_nTotal; i++)
    {
        g_nPoint[i] = i;
    }
}
void randomShuffle(int data[], int length)
{
    int i;
    for (i = 0; i < length; i++)
    {
        swap(data + i, data + i + random(length - i));
    }
}
void generateMap(int nRow, int nCol)
{
    int i, j, k;
    if (g_bMapGenerated) return ;
    randomShuffle(g_nPoint, g_nTotal);
    for (i = k = 0; i < g_nMines && k < g_nTotal; k++)
    {
        int r = g_nPoint[k] / g_nCol;
        int c = g_nPoint[k] % g_nCol;
        if (r != nRow || c != nCol) 
        {
            // count
            ++i;
            // put
            g_realMap[r][c] = '+';
            // update numbers
            for (j = 0; j < 8; j++)
            {
                int rr = r + D[0][j];
                int cc = c + D[1][j];
                if (!out(rr, cc) && g_realMap[rr][cc] != '+')
                {
                    ++g_realMap[rr][cc];
                }
            }
        }
    }
    g_bMapGenerated = TRUE;
    // notify timer
    if (g_fCallbacks.onGameTimer != NULL) g_fCallbacks.onGameTimer();
}
void InitializeGame(int nRow, int nCol, int nMines, GameCallbacks callbacks)
{
    // stop
    StopGame();

    // set
    SetSize(nRow, nCol);
    SetMines(nMines);

    // init random
    initRandom();

    // callbacks
    g_fCallbacks = callbacks;

    // notify
    if (g_fCallbacks.onGameInit != NULL) g_fCallbacks.onGameInit();
}
void StartGame()
{
    if (g_bGameStarted) 
    {
        StopGame();
    }

    // clear
    clearMap();

    g_bGameStarted = TRUE;
    g_bGameOver = FALSE;

    g_nRemain = g_nTotal;
    g_nFlags = 0;

    // notify
    if (g_fCallbacks.onGameStart != NULL) g_fCallbacks.onGameStart();
}
void StopGame()
{
    g_bGameStarted = FALSE;
    g_bGameOver = FALSE;
    g_bMapGenerated = FALSE;

    DoMaskRelease();
}
int IsGameStarted()
{
    return g_bGameStarted;
}
int IsGameOver()
{
    return !g_bGameStarted;
}
int IsGameDead()
{
    return IsGameOver() && !IsAllCleared();
}
int IsAllCleared()
{
    return g_nRemain == g_nMines;
}
void SetSize(int nRow, int nCol)
{
    if (IsGameStarted()) return ;

    g_nRow = ((nRow <= 4 || nRow > MAXN) ? DEF_ROW : nRow);
    g_nCol = ((nCol <= 7 || nCol > MAXN) ? DEF_COL : nCol);
    g_nTotal = g_nRow * g_nCol;
}
int GetRow()
{
    return g_nRow;
}
int GetCol()
{
    return g_nCol;
}
void SetMines(int nMines)
{
    if (IsGameStarted()) return ;
    g_nMines = ((nMines <= 0 || nMines >= g_nTotal) ? g_nTotal / 2 : nMines);
}
int GetMines()
{
    return g_nMines;
}
int GetFlags()
{
    return g_nFlags;
}
char GetCell(int nRow, int nCol)
{
    if (out(nRow, nCol)) return '\0';
    return g_showMap[nRow][nCol];
}
int GetMask(int nRow, int nCol)
{
    if (!IsGameStarted()) return FALSE;
    if (out(nRow, nCol)) return FALSE;
    if (g_nMaskDim < 0) return FALSE;
    return nRow >= g_nMaskRow - g_nMaskDim && nRow <= g_nMaskRow + g_nMaskDim
        && nCol >= g_nMaskCol - g_nMaskDim && nCol <= g_nMaskCol + g_nMaskDim;
}
void open(int nRow, int nCol) // be sure nRow, nCol is valid
{
    int i, j;

    if (g_realMap[nRow][nCol] >= '0' && g_realMap[nRow][nCol] <= '8')
    {
        // success
        g_showMap[nRow][nCol] = g_realMap[nRow][nCol];
        // remain
        --g_nRemain;
        // events
        if (g_fCallbacks.onRefresher != NULL) g_fCallbacks.onRefresher(nRow, nCol);
        // check game over
        if (IsAllCleared())
        {
            // show all flags
            for (i = 0; i < g_nRow; i++)
            {
                for (j = 0; j < g_nCol; j++)
                {
                    if (g_realMap[i][j] == '+' && g_showMap[i][j] != 'f')
                    {
                        g_showMap[i][j] = 'f';
                        g_nFlags++;
                        if (g_fCallbacks.onRefresher != NULL) g_fCallbacks.onRefresher(i, j);
                        if (g_fCallbacks.onFlagsChange != NULL) g_fCallbacks.onFlagsChange(g_nFlags);
                    }
                }
            }
            // stop
            StopGame();
            // on win
            if (g_fCallbacks.onWin != NULL) g_fCallbacks.onWin();
            // return 
            return ;
        }
        // no mines around, expand
        if (g_realMap[nRow][nCol] == '0')
        {
            for (i = 0; i < 8; i++)
            {
                int r = nRow + D[0][i];
                int c = nCol + D[1][i];

                if (out(r, c)) continue; 
                if (g_showMap[r][c] != ' ' && g_showMap[r][c] != '?') continue;
                if (g_realMap[r][c] >= '0' && g_realMap[r][c] <= '8')
                    open(r, c);
            }
        }
    }
    else if (g_realMap[nRow][nCol] == '+')
    {
        // show all mines
        for (i = 0; i < g_nRow; i++)
        {
            for (j = 0; j < g_nCol; j++) 
            {
                // no flag mine
                if (g_realMap[i][j] == '+' && g_showMap[i][j] != 'f')
                {
                    g_showMap[i][j] = '+';
                    if (g_fCallbacks.onRefresher != NULL) g_fCallbacks.onRefresher(i, j);
                }

                // error flag mine
                if (g_realMap[i][j] != '+' && g_showMap[i][j] == 'f')
                {
                    g_showMap[i][j] = 'x';
                    if (g_fCallbacks.onRefresher != NULL) g_fCallbacks.onRefresher(i, j);
                }
            }
        }
        // clicked mine
        g_showMap[nRow][nCol] = '#';
        if (g_fCallbacks.onRefresher != NULL) g_fCallbacks.onRefresher(nRow, nCol);
        // stop game
        StopGame();
        // on lose
        if (g_fCallbacks.onLose != NULL) g_fCallbacks.onLose();
    }
}
void DoOpen(int nRow, int nCol)
{
    if (!IsGameStarted()) return ;
    if (out(nRow, nCol)) return ;
    if (!g_bMapGenerated) generateMap(nRow, nCol);
    if (g_showMap[nRow][nCol] == ' ' || g_showMap[nRow][nCol] == '?')
    {
        open(nRow, nCol);
    }
}
void DoDetect(int nRow, int nCol)
{
    if (!IsGameStarted()) return ;
    if (out(nRow, nCol)) return ;
    if (g_showMap[nRow][nCol] <= '0' || g_showMap[nRow][nCol] > '8') return ; // do not click on empty

    int i, j;
    int nFlag = 0;
    int nSpace = 0;
    int nCount = g_showMap[nRow][nCol] - '0';

    // count
    for (i = 0; i < 8; i++)
    {
        int r = nRow + D[0][i];
        int c = nCol + D[1][i];
        if (out(r, c)) continue;
        nFlag += (g_showMap[r][c] == 'f' ? 1 : 0);
        nSpace += ((g_showMap[r][c] == ' ' || g_showMap[r][c] == '?') ? 1 : 0);
    }
    // auto open
    if (nFlag == nCount)
    {
        for (i = 0; i < 8; i++)
        {
            int r = nRow + D[0][i];
            int c = nCol + D[1][i];
            if (out(r, c)) continue;
            if (g_showMap[r][c] == ' ')
            {
                DoOpen(r, c);
            }
        }
    }
    else if (nFlag + nSpace == nCount)
    {
        for (i = 0; i < 8; i++)
        {
            int r = nRow + D[0][i];
            int c = nCol + D[1][i];
            if (out(r, c)) continue;
            if (g_showMap[r][c] == ' ' || g_showMap[r][c] == '?')
            {
                DoFlag(r, c);
            }
        }
        // check
        for (i = 0; i < 8; i++)
        {
            int r = nRow + D[0][i];
            int c = nCol + D[1][i];
            if (out(r, c)) continue;
            if (g_showMap[r][c] == 'f')
            {
                for (j = 0; j < 8; j++)
                {
                    int rr = r + D[0][j];
                    int cc = c + D[1][j];
                    if (out(rr, cc)) continue;
                    DoDetect(rr, cc);
                }
            }
        }
    }
}
void DoFlag(int nRow, int nCol)
{
    if (!IsGameStarted()) return ;
    if (out(nRow, nCol)) return ;
    switch (g_showMap[nRow][nCol]) 
    {
    case ' ':
        g_showMap[nRow][nCol] = 'f';
        g_nFlags++;
        if (g_fCallbacks.onFlagsChange != NULL) g_fCallbacks.onFlagsChange(g_nFlags);
        break;
    case 'f':
        g_showMap[nRow][nCol] = '?';
        g_nFlags--;
        if (g_fCallbacks.onFlagsChange != NULL) g_fCallbacks.onFlagsChange(g_nFlags);
        break;
    case '?':
        g_showMap[nRow][nCol] = ' ';
        break;
    default:
        return ;
    }
    if (g_fCallbacks.onRefresher != NULL) g_fCallbacks.onRefresher(nRow, nCol);
}

void maskRefresh(int nRow, int nCol, int nDim)
{
    int i, j;
    for (i = -nDim; i <= nDim; i++)
    {
        for (j = -nDim; j <= nDim; j++)
        {
            int r = nRow + i;
            int c = nCol + j;
            if (out(r, c)) continue;
            if (g_showMap[r][c] == ' ' || g_showMap[r][c] == '?')
            {
                if (g_fCallbacks.onRefresher != NULL) g_fCallbacks.onRefresher(r, c);
            }
        }
    }
}

void DoMask(int nRow, int nCol, int nDim)
{
    if (!IsGameStarted()) return ;
    if (nDim < 0) return ;
    if (g_nMaskRow != nRow || g_nMaskCol != nCol || g_nMaskDim != nDim)
    {
        // release old
        DoMaskRelease();
        // set new
        g_nMaskRow = nRow;
        g_nMaskCol = nCol;
        g_nMaskDim = nDim;
        // refresh
        maskRefresh(nRow, nCol, nDim);
    }
}
void DoMaskRelease() // Release Mask
{
    int oldDim = g_nMaskDim;
    // release
    g_nMaskDim = -1;
    // refresh
    maskRefresh(g_nMaskRow, g_nMaskCol, oldDim);
}
